package dev2dev.textclient;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import dev2dev.textclient.voip.RTCRTPHeaderExample;

import java.text.ParseException;
import java.util.*;

import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.ObjectInUseException;
import javax.sip.PeerUnavailableException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.SipListener;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.TransportNotSupportedException;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;


public class SipLayer implements SipListener {

    private MessageProcessor messageProcessor;

    private String username;

    private SipStack sipStack;

    private SipFactory sipFactory;

    private AddressFactory addressFactory;

    private HeaderFactory headerFactory;

    private MessageFactory messageFactory;

    private SipProvider sipProvider;

    private long cSeq = 0;
    /**
     * 音频控制
     **/
    public RTCRTPHeaderExample rtcrtpHeaderExample = new RTCRTPHeaderExample();
//    private Thread thread;
//    public TargetDataLine targetDataLine;
//    public boolean audioSendSta;
//    private static final int SAMPLE_RATE = 8000; // 采样率
//    private static final int BUFFER_SIZE = 1024; // 缓冲区大小

    /**
     * Here we initialize the SIP stack.
     */
    public SipLayer(String username, String ip, int port)
            throws PeerUnavailableException, TransportNotSupportedException,
            InvalidArgumentException, ObjectInUseException,
            TooManyListenersException {
        setUsername(username);
        sipFactory = SipFactory.getInstance();
        sipFactory.setPathName("gov.nist");
        Properties properties = new Properties();
        properties.setProperty("javax.sip.STACK_NAME", "TextClient");
        properties.setProperty("javax.sip.IP_ADDRESS", ip);

        //DEBUGGING: Information will go to files
        //textclient.log and textclientdebug.log
        properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32");
        properties.setProperty("gov.nist.javax.sip.SERVER_LOG",
                "textclient.txt");
        properties.setProperty("gov.nist.javax.sip.DEBUG_LOG",
                "textclientdebug.log");

        sipStack = sipFactory.createSipStack(properties);
        headerFactory = sipFactory.createHeaderFactory();
        addressFactory = sipFactory.createAddressFactory();
        messageFactory = sipFactory.createMessageFactory();

        ListeningPoint tcp = sipStack.createListeningPoint(ip, port, "tcp");
        ListeningPoint udp = sipStack.createListeningPoint(ip, port, "udp");

        sipProvider = sipStack.createSipProvider(tcp);
        sipProvider.addSipListener(this);
        sipProvider = sipStack.createSipProvider(udp);
        sipProvider.addSipListener(this);
    }

    /**
     * This method uses the SIP stack to send a message.
     */
    public void sendMessage(String to, byte[] message, Map<String, String> instructMap) throws ParseException,
            InvalidArgumentException, SipException {

        String username = to.substring(to.indexOf(":") + 1, to.indexOf("@"));
        String address = to.substring(to.indexOf("@") + 1);

        ArrayList viaHeaders = new ArrayList();
        ViaHeader viaHeader = headerFactory.createViaHeader(address.split(":")[0], getPort(), "UDP", RandomUtil.randomString(12));
        viaHeaders.add(viaHeader);

        String fromTag = instructMap.get("fromTag");
        SipURI from = addressFactory.createSipURI(getUsername(), getHost());
        Address fromNameAddress = addressFactory.createAddress(from);
        FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, fromTag != null ? fromTag : RandomUtil.randomString(10));
        instructMap.remove("fromTag");

        SipURI toAddress = addressFactory.createSipURI(username, address);
        Address toNameAddress = addressFactory.createAddress(toAddress);
//        toNameAddress.setDisplayName("Bob");
        ToHeader toHeader = headerFactory.createToHeader(toNameAddress, null);
        SipURI requestURI = addressFactory.createSipURI(username, address);


        String callId = instructMap.get("callId");
        CallIdHeader callIdHeader = sipProvider.getNewCallId();
        if (callId != null) {
            callIdHeader.setCallId(callId);
            instructMap.remove("callId");
        } else {
            callIdHeader.setCallId(RandomUtil.randomNumbers(32));
        }


        String methodType = instructMap.get("methodType");
        CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(cSeq++,
                StrUtil.isBlank(methodType) ? Request.MESSAGE : methodType);

        MaxForwardsHeader maxForwards = headerFactory
                .createMaxForwardsHeader(70);
        Request request = messageFactory.createRequest(requestURI,
                StrUtil.isBlank(methodType) ? Request.MESSAGE : methodType, callIdHeader, cSeqHeader, fromHeader,
                toHeader, viaHeaders, maxForwards);

        instructMap.remove("methodType");
        SipURI contactURI = addressFactory.createSipURI(getUsername(),
                getHost());
        contactURI.setPort(getPort());
        Address contactAddress = addressFactory.createAddress(contactURI);
//        contactAddress.setDisplayName("Alice");
        ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
        if (methodType.equals(Request.REGISTER)) {
            contactHeader.setExpires(120);
        }
        request.addHeader(contactHeader);

        Set keyset = instructMap.keySet();
        for (Object key : keyset) {
            if ("Content-Type".equals(key)) {
                continue;
            } else {
                Header header = headerFactory.createHeader(key.toString(), instructMap.get(key));
                request.addHeader(header);
            }
        }
        if (instructMap.get("Content-Type") != null) {
            String[] typ = instructMap.get("Content-Type").split("/");
            ContentTypeHeader contentTypeHeader = headerFactory
                    .createContentTypeHeader(typ[0], typ[1]);
            request.setContent(message, contentTypeHeader);
        } else {
            if (message.length > 0) {
                ContentTypeHeader contentTypeHeader = headerFactory
                        .createContentTypeHeader("text", "plain");
                request.setContent(message, contentTypeHeader);
            }
        }
//        instructMap = new HashMap<>();
        System.out.println("request send");
        System.out.println(request);
        if(request.getMethod().equals(Request.INVITE)){
             rtcrtpHeaderExample.setRequest(request);
        }
        sipProvider.sendRequest(request);

        messageProcessor.processInfo("--Request >>>");
        messageProcessor.processInfo(request.toString());
    }

    /**
     * This method is called by the SIP stack when a response arrives.
     */
    public void processResponse(ResponseEvent evt) {
        Response response = evt.getResponse();
        int status = response.getStatusCode();

//        System.out.println("response:");
//        System.out.println(response);

        if (status == Response.RINGING) {
            System.out.println(status);
            messageProcessor.processInfo(status + "响铃~~");

        }
        String method = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)).getMethod();
        if ((status >= Response.OK) && (status < Response.MULTIPLE_CHOICES)) { //Success!
            if (method.equals(Request.REGISTER)) {
                //1.注册成功 发送等待再一次发送心跳
//                HashMap<String, String> map = new HashMap<>();
//                map.put("methodType", Request.OPTIONS);
//                String to = ((ToHeader) response.getHeader(ToHeader.NAME))
//                        .getAddress().getURI().toString() + ":" + getPort();
//                try {
//                    sendMessage(to, new byte[0], map);
//                } catch (Throwable e) {
//                    e.printStackTrace();
//                    System.out.println("再一心跳失败");
//                }
            } else if (method.equals(Request.INVITE)) {
                //1.中继器音频接收端口启动成功
                HashMap<String, String> map = new HashMap<>();
                String to = ((ToHeader) response.getHeader(ToHeader.NAME)).getAddress().getURI().toString();
                String from = ((FromHeader) response.getHeader(FromHeader.NAME)).getAddress().getURI().toString();
                String fromTag = ((FromHeader) response.getHeader(FromHeader.NAME)).getTag();
                CallIdHeader callId = ((CallIdHeader) response.getHeader(CallIdHeader.NAME));
                try {

//                    try {
//                        Thread.sleep(300);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }

                    if (rtcrtpHeaderExample.getAudioSendSta()==1) {
                        //2.发送响铃
//                        map.put("methodType", Request.ACK);
//                        map.put("callId", callId.getCallId());
//                        map.put("fromTag", fromTag);

                        Request request =  rtcrtpHeaderExample.getRequest();
                        request.setMethod(Request.ACK);
                        sipProvider.sendRequest(request);
//                        sendMessage(to + ":" + getPort(), new byte[0], map);

                        String toUsername = to.substring(to.indexOf(":") + 1, to.indexOf("@"));
                        String fromUsername = from.substring(from.indexOf(":") + 1, from.indexOf("@"));
                        //3.启动音频发送
                        String content = response.toString();
                        String audioRtcVo = content.substring(content.indexOf("m=audio ") + 7, content.indexOf(" RTP/AVP ")).replace(" ", "");
                        System.out.println("INVITE发送端口：" + audioRtcVo);
                        rtcrtpHeaderExample.initiateAudio(Integer.parseInt(audioRtcVo), Integer.parseInt(toUsername), Integer.parseInt(fromUsername));
                    }
                } catch (Throwable e) {
                    e.printStackTrace();
                    System.out.println("会话建立报错：" + to);
                }

            }
            //5.应答正常
            messageProcessor.processInfo("--Response >>>");
            messageProcessor.processInfo(response.toString());
            return;
        } else if (response.getStatusCode() == Response.BAD_REQUEST) {
            try {
                if (method.equals(Request.OPTIONS)) {
                    HashMap<String, String> map = new HashMap<>();
                    map.put("methodType", Request.REGISTER);
                    String to = ((ToHeader) response.getHeader(ToHeader.NAME))
                            .getAddress().getURI().toString() + ":" + getPort();
                    sendMessage(to, new byte[0], map);
                }
            } catch (Throwable e) {
                e.printStackTrace();
                System.out.println("心跳失败后注册发送失败");
            }

        } else if (response.getStatusCode() == Response.SERVER_INTERNAL_ERROR) {
            System.out.println("中继服务器失败(5XX) 500 server internal error 中继服务器内部错误");
        }

        messageProcessor.processError("Previous message not sent: " + status);
    }

    /**
     * This method is called by the SIP stack when a new request arrives.
     */
    public void processRequest(RequestEvent evt) {
        Request req = evt.getRequest();

        String method = req.getMethod();
//        if (!method.equals("MESSAGE")) { //bad request type.
//            messageProcessor.processError("Bad request type: " + method);
//            return;
//        }
        System.out.println("processRequest:");
        System.out.println(evt.getRequest());
//        FromHeader from = (FromHeader) req.getHeader("From");
        messageProcessor.processInfo(req.toString());

        if (method.equals(Request.BYE)) {
            rtcrtpHeaderExample.setAudioSendSta(0);
            String from = ((FromHeader) req.getHeader(FromHeader.NAME)).getAddress().getURI().toString();
            System.out.println("接收会话已断开(Session disconnected)BYE：from=" + from);
//            return;
        }
//        messageProcessor.processMessage(from.getAddress().toString(),
//                new String(req.getRawContent()));

//        if(method.equals(Request.OPTIONS)){
//            try { //Reply with OK
//                response = messageFactory.createResponse(200, req);
//                ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
//                toHeader.setTag(RandomUtil.randomString(10)); //This is mandatory as per the spec.
//                ServerTransaction st = sipProvider.getNewServerTransaction(req);
//                st.sendResponse(response);
//            } catch (Throwable e) {
//                e.printStackTrace();
//                messageProcessor.processError("Can't send OK reply.");
//            }
//        }
        Response response = null;
        try { //Reply with OK
            response = messageFactory.createResponse(200, req);
            if (method.equals(Request.OPTIONS)) {
//                if (rtcrtpHeaderExample.audioSendSta) {
//                    try {
//                        Header header = headerFactory.createHeader("Allow", "INVITE,ACK,CANCEL,BYE,MESSAGE");
//                        response.addHeader(header);
//                    } catch (ParseException e) {
//                        e.printStackTrace();
//                    }
////                map.put("Allow", "INVITE, ACK, CANCEL, BYE, MESSAGE");
//                }
            }
            ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
            toHeader.setTag(RandomUtil.randomString(10)); //This is mandatory as per the spec.
            ServerTransaction st = sipProvider.getNewServerTransaction(req);
            st.sendResponse(response);
        } catch (Throwable e) {
            e.printStackTrace();
            messageProcessor.processError("Can't send OK reply.");
        }
    }

    public void sendRequest(Request request) throws SipException {
        sipProvider.sendRequest(request);
    }

    /**
     * This method is called by the SIP stack when there's no answer
     * to a message. Note that this is treated differently from an error
     * message.
     */
    public void processTimeout(TimeoutEvent evt) {
        messageProcessor
                .processError("Previous message not sent: " + "timeout");
    }

    /**
     * This method is called by the SIP stack when there's an asynchronous
     * message transmission error.
     */
    public void processIOException(IOExceptionEvent evt) {
        messageProcessor.processError("Previous message not sent: "
                + "I/O Exception");
    }

    /**
     * This method is called by the SIP stack when a dialog (session) ends.
     */
    public void processDialogTerminated(DialogTerminatedEvent evt) {
    }

    /**
     * This method is called by the SIP stack when a transaction ends.
     */
    public void processTransactionTerminated(TransactionTerminatedEvent evt) {
    }

    public String getHost() {
        int port = sipProvider.getListeningPoint().getPort();
        String host = sipStack.getIPAddress();
        return host;
    }

    public int getPort() {
        int port = sipProvider.getListeningPoint().getPort();
        return port;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String newUsername) {
        username = newUsername;
    }


    public MessageProcessor getMessageProcessor() {
        return messageProcessor;
    }

    public void setMessageProcessor(MessageProcessor newMessageProcessor) {
        messageProcessor = newMessageProcessor;
    }
}
